/*------------------------------------------------------------------------------
* Copyright (C) 2006 Jos van den Oever <jos@vandenoever.info>
*
* Distributable under the terms of either the Apache License (Version 2.0) or
* the GNU Lesser General Public License, as specified in the LICENSE file.
------------------------------------------------------------------------------*/
#ifndef STREAMBASE_H
#define STREAMBASE_H

#include <string>

#define INT32MAX 0x7FFFFFFFL

namespace jstreams
{

enum StreamStatus { Ok, Eof, Error };

/**
 * @short Base class for stream read access to many different file types.
 *
 * This class is based on the interface java.io.InputStream. It allows
 * for uniform access to streamed resources.
 * The main difference with the java equivalent is a performance improvement.
 * When reading data, data is not copied into a buffer provided by the caller,
 * but a pointer to the read data is provided. This makes this interface
 * especially useful for deriving from it and implementing filterers or
 * transformers.
 */
// java mapping: long=int64, int=int32, byte=uint8_t
template <class T>
class StreamBase
{
protected:
	int64_t size;
	int64_t position;
	std::string error;
	StreamStatus status;
public:
	StreamBase() : size(-1), position(0), status(Ok) { }
	virtual ~StreamBase() {}
	/**
	 * @brief  Return a string representation of the last error.
	 * If no error has occurred, an empty string is returned.
	 **/
	const char* getError() const {
		return error.c_str();
	}
	StreamStatus getStatus() const {
		return status;
	}
	/**
	 * @brief Get the current position in the stream.
	 * The value obtained from this function can be used to reset the stream.
	 **/
	int64_t getPosition() const {
		return position;
	}
	/**
	 * @brief Return the size of the stream.
	 * If the size of the stream is unknown, -1
	 * is returned. If the end of the stream has been reached the size is
	 * always known.
	 **/
	int64_t getSize() const {
		return size;
	}
	/**
	 * @brief Reads characters from the stream and sets \a start to
	 * the first character that was read.
	 *
	 * If @p ntoread is @c 0, then at least one character will be read.
	 *
	 * @param start Pointer passed by reference that will be set to point to
	 *              the retrieved array of characters. If the end of the stream
	 *              is encountered or an error occurs, the value of @p start
	 *              is undefined.
	 * @param min The number of characters to read from the stream.
	 * @param max The maximum number of characters to read from the stream.
	 * @return the number of characters that were read. If -1 is returned, the
	 *         end of the stream has been reached. If -2 is returned, an error
	 *         has occurred.
	 **/
	virtual int32_t read(const T*& start, int32_t min, int32_t max) = 0;
	/**
	 * Skip @param ntoskip bytes. Unless an error occurs or the end of file is
	 * encountered, this amount of bytes is skipped.
	 * This function returns new position in the stream.
	 **/
	virtual int64_t skip(int64_t ntoskip);
	/**
	 * @brief Repositions this stream to given requested position.
	 * Reset is guaranteed to work after a successful call to read(),
	 * when the new position is in the range of the data returned by read().
	 * This means that @p pos must lie between than the position
	 * corresponding to the start parameter (x) of the read function
	 * and the position corresponding to the last position in the returned
	 * buffer (x + nread).
	 **/
	virtual int64_t reset(int64_t pos) = 0;
	int64_t mark(int32_t readlimit) {
		int64_t p = getPosition();
		const T* ptr;
		read(ptr, readlimit, -1);
		return reset(p);
	}
};
#define SKIPSTEP 1024
template <class T>
int64_t
StreamBase<T>::skip(int64_t ntoskip)
{
	const T *begin;
	int32_t nread;
	int64_t skipped = 0;
	while (ntoskip) {
		int32_t step = (int32_t)((ntoskip > SKIPSTEP) ? SKIPSTEP : ntoskip);
		nread = read(begin, 1, step);
		if (nread < -1) {
			// an error occurred
			return nread;
		}
		else if (nread < 1) {
			ntoskip = 0;
		}
		else {
			skipped += nread;
			ntoskip -= nread;
		}
	}
	return skipped;
}

} // end namespace jstreams

#endif
